// file: driver/block/loop.c
// autor: jiangxinpeng
// time: 2021.5.25
// copyright: 2020-2050 by jiangxinpeng,All right are reserved.

#include <driver/loop.h>
#include <os/driver.h>
#include <os/memcache.h>
#include <os/kernelif.h>
#include <os/mutexlock.h>
#include <os/initcall.h>
#include <os/debug.h>
#include <os/path.h>
#include <os/initcall.h>
#include <os/diskman.h>
#include <lib/unistd.h>
#include <lib/type.h>
#include <lib/list.h>
#include <lib/string.h>
#include <lib/stdio.h>
#include <lib/unistd.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>

static int LoopSetup(device_object_t *dev, char *path);
static int LoopSetdown(device_object_t *dev);

static void LoopExtensionInit(device_extension_t *extension)
{
    extension->glofd = -1;
    extension->sectors = 0;
    extension->flags = 0;
    extension->rwoff = 0;
    memset(extension->image_file, 0, LOOP_IMAGE_PATH_LEN);
}

static iostatus_t LoopEnter(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;

    device_object_t *devobj;
    device_extension_t *extension;
    char devname[DEVICE_NAME_LEN+1];

    for (int i = 0; i < LOOP_DEVICE_NUM; i++)
    {
        memset(devname, 0, DEVICE_NAME_LEN);
        sprintf(devname, "%s%d", DEVICE_NAME, i);
        status = IoCreateDevice(driver, sizeof(device_extension_t), devname, DEVICE_TYPE_VIRTUAL_DISK, &devobj);
        if (status != IO_SUCCESS)
        {
            KPrint(PRINT_ERR "%s: create device failed!\n", __func__);
            return status;
        }
        devobj->flags = 0;
        extension = devobj->device_extension;
        LoopExtensionInit(extension);
    }
    return status;
}

static iostatus_t LoopExit(driver_object_t *driver)
{
    device_object_t *next, *p;

    list_traversal_all_owner_to_next_safe(p, next, &driver->device_list, list)
    {
        IoDeleteDevice(p);
    }
    string_del(&driver->name);
    return IO_SUCCESS;
}

static iostatus_t LoopOpen(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info=0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t LoopClose(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    ioreq->io_status.status = status;
    ioreq->io_status.info=0;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t LoopWrite(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension = device->device_extension;
    uint32_t off=0, len=0;
    int writes=-1;

    // loop device vaild
    if (!IS_LOOP_USING(extension) || extension->glofd < 0)
    {
        status = IO_FAILED;
        goto final;
    }
    off = ioreq->parame.write.offset;
    if (off >= DISKOFF_MAX)
    {
        off = 0;
    }
    len = ioreq->parame.write.len;

    KPrint("loop: write off %u len %u\n",(uint32_t)off,(uint32_t)len);

    // check limit
    if (off + (len / SECTOR_SIZE) >= extension->sectors)
    {
        status = IO_FAILED;
    }
    else
    {
        KPrint("loop: write disk,globalfd %d\n",extension->glofd);
        // doing disk write
        KFileLSeek(extension->glofd, off * SECTOR_SIZE, SEEK_SET);
        writes = KFileWrite(extension->glofd, ioreq->user_buff, len);
        if (writes <= 0)
        {
            KPrint("%s: loop device write image file %s failed!\n", __func__, extension->image_file);
            status = IO_FAILED;
            goto final;
        }
        ioreq->io_status.info = writes;
    }
final:
    ioreq->io_status.status = status;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t LoopRead(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status = IO_SUCCESS;
    device_extension_t *extension = device->device_extension;
    uint64_t off=0, len=0;
    int reads=-1;

    // loop device vaild
    if (!IS_LOOP_USING(extension) || extension->glofd < 0)
    {
        status = IO_FAILED;
        goto final;
    }
    off = ioreq->parame.read.offset;
    if (off >= DISKOFF_MAX)
    {
        off = 0;
    }
    len = ioreq->parame.read.len;
    // check limit
    if (off + (len / SECTOR_SIZE) >= extension->sectors)
    {
        status = IO_FAILED;
    }
    else
    {
        // doing disk read
        KFileLSeek(extension->glofd, off * SECTOR_SIZE, SEEK_SET);
        reads = KFileRead(extension->glofd, ioreq->user_buff, len);
        if (reads <= 0)
        {
            KPrint("%s: loop device read image file %s failed!\n", __func__, extension->image_file);
            status = IO_FAILED;
            goto final;
        }
        ioreq->io_status.info = reads;
    }
final:
    ioreq->io_status.status = status;
    IoCompleteRequest(ioreq);
    return status;
}

static iostatus_t LoopDevCtl(device_object_t *device, io_request_t *ioreq)
{
    iostatus_t status=IO_SUCCESS;
    device_extension_t *extension = device->device_extension;
    uint32_t code = ioreq->parame.devctl.code;
    uint32_t arg = ioreq->parame.devctl.arg;

    switch (code)
    {
    case DISKIO_SETUP:
        if(LoopSetup(device, (void *)arg)<0)
        {
            KPrint("[loop] setup: device %s path %s error\n",device->name,arg);
            status=IO_FAILED;
        }
        DiskAdd(device,DISK_TYPE_DISK);
        break;
    case DISKIO_GETSIZE:
        if (!IS_LOOP_USING(extension))
        {
            *(uint32_t *)arg = 0;
            status = IO_FAILED;
            goto final;
        }
        *(uint32_t *)arg = extension->sectors;
        break;
    case DISKIO_SETOFF:
        if (!IS_LOOP_USING(extension))
        {

            status = IO_FAILED;
            goto final;
        }
        (extension->rwoff < (extension->sectors - 1)) ? (extension->rwoff = *(uint32_t *)arg) : (extension->rwoff = extension->sectors - 1);
        break;
    case DISKIO_GETOFF:
        if (!IS_LOOP_USING(extension))
        {
            *(uint32_t *)arg = 0;
            status = IO_FAILED;
            goto final;
        }
        *(uint32_t *)arg = extension->rwoff;
        break;
    case DISKIO_SETDOWN:
        if (LoopSetdown(device) < 0)
        {
            KPrint("[loop] setdown device %s failed\n",device->name);
            status = IO_FAILED;
            goto final;
        }
        DiskDel(device);
        break;
    default:
        status = IO_FAILED;
        break;
    }
final:
    ioreq->io_status.status = status;
    ioreq->io_status.info=0;
    IoCompleteRequest(ioreq);
    return status;
}

static int LoopSetup(device_object_t *device, char *path)
{
    device_extension_t *extension = device->device_extension;
    char abspath[MAX_PATH_LEN+1];
    int fd=-1, size=0;
    if (IS_LOOP_USING(extension))
    {
        KPrint(PRINT_ERR "%s: loop device %s is using\n", __func__, &device->name.text);
        return -1;
    }
    if(!path||!path[0])
    {
        KPrint(PRINT_ERR"%s: loop path is null\n");
        return -1;
    }
    BuildPath(path, abspath);
    // get file info
    fd = KFileOpen(abspath, O_RDWR);
    if (fd < 0)
    {
        return -1;
    }
    #if DEBUG_LOOP_SETUP
    KPrint("loop: set path %s\n",abspath);
    #endif
    // check file size
    //KFileLSeek(fd, 0, SEEK_END);
    //size = KFileFTell(fd);
    status_t fstu;
    if(KFileStatus(abspath,&fstu)<0)
    {
        KPrint("%s: get file status %d errror\n",abspath);
        KFileClose(fd);
        return -1;
    }
    size=fstu.st_size;
    if (size < SECTOR_SIZE)
    {
        KPrint("%s: loop setup file %s size %d too small\n", __func__, abspath,size);
        KFileClose(fd);
        return -1;
    }
    // record file info and enable device
    extension->glofd = fd;
    extension->sectors = size / SECTOR_SIZE;
    extension->flags |= LOOP_FLAG_USING;
    strncpy(extension->image_file, abspath, min(strlen(abspath),LOOP_IMAGE_PATH_LEN));
    extension->image_file[min(strlen(abspath),LOOP_IMAGE_PATH_LEN)] = '\0';
    #if DEBUG_LOOP_SETUP
    KPrint("loop: setup path %s [%s] ok,sector %d\n",abspath,extension->image_file,extension->sectors);
    #endif
    return 0;
}

static int LoopSetdown(device_object_t *device)
{
    device_extension_t *extension = device->device_extension;

    if (!IS_LOOP_USING(extension))
    {
        KPrint("loop: setdown %s device no using!\n", device->name.text);
        return -1;
    }
    if (extension->glofd < 0 || !extension->sectors)
    {
        KPrint("loop: setdown %s device extension info error!\n", device->name.text);
        return -1;
    }
    KFileClose(extension->glofd);
    LoopExtensionInit(extension);
    return 0;
}

static iostatus_t LoopDriverFunc(driver_object_t *driver)
{
    iostatus_t status = IO_SUCCESS;

    driver->driver_enter = LoopEnter;
    driver->driver_exit = LoopExit;

    driver->dispatch_fun[IOREQ_OPEN] = LoopOpen;
    driver->dispatch_fun[IOREQ_CLOSE] = LoopClose;
    driver->dispatch_fun[IOREQ_READ] = LoopRead;
    driver->dispatch_fun[IOREQ_WRITE] = LoopWrite;
    driver->dispatch_fun[IOREQ_DEVCTL] = LoopDevCtl;

    string_new(&driver->name, DRIVER_NAME, DRIVER_NAME_LEN);

    return status;
}

static __init void LoopDriverEntry()
{
    KPrint("[driver] create loop driver\n");
    if (DriverObjectCreate(LoopDriverFunc) < 0)
    {
        KPrint(PRINT_ERR "[driver] Loop Driver create failed!\n");
    }
}

driver_initcall(LoopDriverEntry);
